#02_家庭用电预测：线性回归算法(时间与电压的多项式关系)
import time
import numpy as np
import pandas as pd
from pandas import Series
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

## 设置字符集，防止中文乱码
mpl.rcParams['font.sans-serif']=[u'simHei']
mpl.rcParams['axes.unicode_minus']=False

# 日期、时间、有功功率、无功功率、电压、电流、厨房用电功率、洗衣服用电功率、热水器用电功率
columns = ['Date', 'Time', 'Global_active_power', 'Global_reactive_power', 'Voltage', 'Global_intensity',
           'Sub_metering_1', 'Sub_metering_2', 'Sub_metering_3']

def date_format(dt):
    ## 创建一个时间字符串格式化字符串
    t=time.strptime(' '.join(dt),'%d/%m/%Y %H:%M:%S')
    return (t.tm_year,t.tm_mon,t.tm_mday,t.tm_hour,t.tm_min,t.tm_sec)

def run():
    # 加载数据
    path = '../data/household_power_consumption_1000.txt'
    df_1 = pd.read_csv(path, sep=';', low_memory=False)
    # 异常数据处理(异常数据过滤)
    df_2 = df_1.replace('?', np.nan)
    datas = df_2.dropna(axis=0, how='any')  # 只要有数据为空，就进行删除操作
    ## 时间和电压之间的关系(Linear)
    # 获取x和y变量, 并将时间转换为数值型连续变量
    X=datas[columns[0:2]]
    X=X.apply(lambda x:Series(date_format(x)),axis=1)
    Y=datas[columns[4]]
    # 对数据集进行测试集合训练集划分
    X_train,X_test,Y_train,Y_test=train_test_split(X,Y,test_size=0.2,random_state=0)
    # 数据标准化
    ss=StandardScaler()
    X_train=ss.fit_transform(X_train)  # 训练并转换
    X_test=ss.transform(X_test)  ## 直接使用在模型构建数据上进行一个数据标准化操作
    # 模型训练
    lr=LinearRegression()
    lr.fit(X_train,Y_train) ## 训练模型
    # 模型校验
    Y_predict=lr.predict(X_test)  ## 预测结果
    # 模型效果
    print("准确率:", lr.score(X_test,Y_test))
    ## 预测值和实际值画图比较
    k=np.arange(len(X_test))
    plt.figure(figsize=(8,6),facecolor='w')
    plt.plot(k,Y_test,'r-',linewidth=2,label=u'真实值')
    plt.plot(k,Y_predict,'g-',linewidth=2,label=u'预测值')
    plt.legend(loc='lower right')
    plt.title(u"线性回归预测时间和功率之间的关系",fontsize=20)
    plt.grid(b=True) # 网格
    plt.show()

    # Pipeline：管道的意思，讲多个操作合并成为一个操作
    # Pipleline总可以给定多个不同的操作，给定每个不同操作的名称即可，执行的时候，按照从前到后的顺序执行
    models = [
        Pipeline([
            ('Poly', PolynomialFeatures()),
            ('Linear', LinearRegression(fit_intercept=False))
        ])
    ]
    model = models[0]
    # 获取x和y变量, 并将时间转换为数值型连续变量
    X = datas[columns[0:2]]
    X = X.apply(lambda x: Series(date_format(x)), axis=1)
    Y = datas[columns[4]]
    # 对数据集进行测试集合训练集划分
    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=0)
    # 数据标准化
    ss = StandardScaler()
    X_train = ss.fit_transform(X_train)  # 训练并转换
    X_test = ss.transform(X_test)  # 直接使用在模型构建数据上进行一个数据标准化操作
    # 模型训练
    k = np.arange(len(X_test))
    N = 5
    d_pool = np.arange(1, N, 1)  # 阶
    m = d_pool.size
    clrs = []  # 颜色
    for c in np.linspace(16711680, 255, m):
        clrs.append('#%06x' % int(c))
    linewidth = 2
    plt.figure(figsize=(12, 6), facecolor='w')  # 创建一个绘图窗口，设置大小，设置颜色
    for i, d in enumerate(d_pool):
        plt.subplot(N - 1, 1, i + 1)
        plt.plot(k, Y_test, 'r-', label=u'真实值', ms=10, zorder=N)
        ### 设置管道对象中的参数值，Poly是在管道对象中定义的操作名称， 后面跟参数名称；中间是两个下划线
        model.set_params(Poly__degree=d)  ## 设置多项式的阶乘
        model.fit(X_train, Y_train)  # 模型训练
        # Linear是管道中定义的操作名称
        # 获取线性回归算法模型对象
        lin = model.get_params('Linear')['Linear']
        output = u'%d阶，系数为：' % d
        # 判断lin对象中是否有对应的属性
        if hasattr(lin, 'alpha_'):
            idx = output.find(u'系数')
            output = output[:idx] + (u'alpha=%.6f,' % lin.alpha_) + output[idx:]
        if hasattr(lin, 'l1_ratio_'):
            idx = output.find(u'系数')
            output = output[:idx] + (u'l1_ratio=%.6f,' % lin.l1_ratio_) + output[idx:]
        print(output, lin.coef_.ravel())
        # 模型结果预测
        Y_predict = model.predict(X_test)
        # 计算评估值
        s = model.score(X_test, Y_test)
        # 画图
        z = N - 1 if (d == 2)else 0
        label = u'%d阶，准确率=%.3f' % (d, s)
        plt.plot(k, Y_predict, color=clrs[i], lw=linewidth, alpha=0.75, label=label, zorder=z)
        plt.legend(loc='upper left')
        plt.grid(True)
        plt.ylabel(u'%d阶结果' % d, fontsize=12)
    ## 预测值和实际值画图比较
    plt.suptitle(u'线性回归预测时间和功率之间的多项式关系', fontsize=20)
    plt.grid(b=True)
    plt.show()

    # 获取x和y变量, 并将时间转换为数值型连续变量
    X = datas[columns[0:2]]
    X = X.apply(lambda x: Series(date_format(x)), axis=1)
    Y = datas[columns[4]]

    # 对数据集进行测试集合训练集划分
    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=0)

    # 数据标准化
    ss = StandardScaler()
    X_train = ss.fit_transform(X_train)  # 训练并转换
    X_test = ss.transform(X_test)  # 直接使用在模型构建数据上进行一个数据标准化操作

    # 多项式扩展
    pf = PolynomialFeatures(degree=3, interaction_only=True)
    train1 = pf.fit_transform(X_train)
    test1 = pf.transform(X_test)

    # 模型训练
    lr = LinearRegression(fit_intercept=False)
    lr.fit(train1, Y_train)
    print(lr.score(test1, Y_test))
    print(lr.coef_)
    print(lr.intercept_)


run()